package textfileproces.arff;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.List;

import textfileproces.FileConversion;
import util.Pair;

/**
 * @author manuel
 * This class performs the transformation between CSV and ARFF formats.
 */
public class CSV2ARFF extends FileConversion {

	// Constants
	private final String COMMA = ",";
	private final String QUOTATION = "\"";
	private final char QUOTATION_CHAR = '"';
	private final char WHITE_CHAR = ' ';
	private final String TXT_EXT = ".txt";
	private final String EOL = "\n";
	private final String ATTRIBUTES = "Attributes";
	private final String LINUX_DIRECTORY_SEPARATOR = "/";
	private final String WINDOWS_DIRECTORY_SEPARATOR = "\\";
	
	private final String[] ABSENT_VALUES = {"n.d.", "null"};
	
	private final String ABSENT_VALUE_ARFF = "?";
	
	private final String T_RELATION = "@RELATION";
	private final String T_ATTRIBUTE = "@ATTRIBUTE";
	private final String T_DATA = "@DATA";
	private final String T_STRING = "STRING";
	private final String T_NUMERIC = "NUMERIC";
	private final String T_DATE = "DATE";
	private final String T_DATE_DEFAULT_FORMAT = "yyyy-MM-dd HH:mm:ss";
	
	// Constructor
	/**
	 * @param inputFileName
	 * @param outputFileName
	 * @throws IOException
	 */
	public CSV2ARFF(String inputFileName, String outputFileName) 
			throws IOException {
    	super(inputFileName, outputFileName);
	}

	// Methods
	public void run() throws IOException {
		String relationName = getRelationName();
		List<Pair<ARFF_DataType, Object>> attributesTypes = 
				writeFileWihtAttributes(relationName);
		writeHeader(relationName, attributesTypes);
		writeData();
	}

	/**
	 * This methods reads the first line of the input file and if the attributes
	 * files does not exists, tries to write a file with the attributes. 
	 * Then it executes an editor to write, by hand, the type of each 
	 * attribute; if the attributes file exists, it skips this step.
	 * @param relationName
	 * @throws IOException 
	 */
	private List<Pair<ARFF_DataType, Object>> writeFileWihtAttributes(
			String relationName) throws IOException {
		String fileWithAttributesName = getFileWithAttributesName(relationName);
		if (!Files.exists(
				Paths.get(fileWithAttributesName), LinkOption.NOFOLLOW_LINKS)) {
			List<String> attributesNames = getCommaSeparatedStrings(
					reader.readLine());
			Path attributesTypesPath = Paths.get(fileWithAttributesName);
			BufferedWriter attributesWriter = Files.newBufferedWriter(
				attributesTypesPath, charset, StandardOpenOption.CREATE_NEW);
			for (String attributeName : attributesNames) {
				attributesWriter.write(attributeName + COMMA + EOL);
			}
			attributesWriter.close();
			displayEditor(attributesTypesPath);
			
			List<Pair> attributesTypes = new ArrayList<Pair>();
			
		}
		List<Pair<ARFF_DataType, Object>> attributesTypes = 
				readFileWithAttributes(fileWithAttributesName);
		return attributesTypes;
	}

	private void displayEditor(Path attributesTypesPath) {
		// TODO Auto-generated method stub
		
	}

	/**
	 * @param fileWithAttributesName
	 * @return List with a description of attributes. <code>List</code> of 
	 *   <code>Pair</code> of <code>String</code> (attribute name) and <code>Object</code>, 
	 *   that will be<li></li> <null> with numeric and string types, a string with the date type and a list of strings with the enumerate type.
	 * @throws IOException
	 */
	private List<Pair<ARFF_DataType, Object>> readFileWithAttributes(String fileWithAttributesName) throws IOException {
		BufferedReader attributesReader = Files.newBufferedReader(
				Paths.get(fileWithAttributesName), charset);
		List<Pair<ARFF_DataType, Object>> attributesList = new ArrayList<Pair<ARFF_DataType, Object>>();
		String line = null;
		while ((line = attributesReader.readLine()) != null) {
			attributesList.add(parseLine(line)); 
		}
		return attributesList;
	}

	private Pair<ARFF_DataType, Object> parseLine(String line) throws IOException {
		Pair<String, Integer> element = parseElement(line, 0);
		// TODO
		return null;
	}

	private Pair<String, Integer> parseElement(String line, int place) throws IOException {
		String elementName = null;
		if (line.charAt(place) == '"') {
			int nextPlace = line.indexOf(line, place + 1);
			if (nextPlace == -1) {
				throw new IOException("Missing quotation.");
			}
			elementName = line.substring(place, nextPlace);
		}
		// TODO Auto-generated method stub
		return null;
	}

	/**
	 * This method gets a name for a new file from the name of other file. 
	 * @param relationName
	 * @return Name of the new file.
	 */
	private String getFileWithAttributesName(String relationName) {
		String fileWithAttributesName = null;
		if (relationName.endsWith(TXT_EXT)) {
			fileWithAttributesName = new String(relationName.substring(
				0, relationName.lastIndexOf(TXT_EXT)) + ATTRIBUTES + TXT_EXT);
		} else {
			fileWithAttributesName = new String(relationName + TXT_EXT);
		}
		return fileWithAttributesName;
	}

	private void writeHeader(String relationName, List<Pair<ARFF_DataType, Object>> attributesTypes)
			throws IOException {
		writer.write(T_RELATION + " " + relationName + "\n\n");

		// The first line contains the attributes
		for (Pair<?, ?> attributeName : attributesTypes) {
			writer.write(T_ATTRIBUTE + " " + attributeName.get(0) + " ");
			writer.write(attributeName.get(1).toString());
		}
	}

	/**
	 * @param inputFileName
	 * @return table name. <code>String</code>
	 */
	private String getRelationName() {
		String relationName = null;
		int lastLinuxSeparator = 
				inputFileName.lastIndexOf(LINUX_DIRECTORY_SEPARATOR);
		int lastWindowsSeparator;
		if (lastLinuxSeparator != -1) {
			relationName = inputFileName.substring(lastLinuxSeparator + 1);
		} else {
			lastWindowsSeparator = 
					inputFileName.lastIndexOf(WINDOWS_DIRECTORY_SEPARATOR);
			if (lastWindowsSeparator != -1) {
				relationName = inputFileName.substring(lastWindowsSeparator + 1);
			} else {
				relationName = new String(inputFileName);
			}
		}
		return relationName;
	}

	private void writeData() throws IOException {
		String line = null;
		while ((line = reader.readLine()) != null) {
			writeLineData(getCommaSeparatedStrings(line));
		}
	}

	private void writeLineData(List<String> commaSeparatedStrings) {
		// TODO
	}

	/**
	 * @param line A <code>string</code> with comma separated values
	 * @return A list of strings with the values in <code>line</code>
	 */
	private List<String> getCommaSeparatedStrings(String line) {
		int fromIndex = 0;
		List<String> attributes = new ArrayList<String>();
		while (fromIndex >= 0) {
			int endIndex = line.indexOf(COMMA, fromIndex);
			if (endIndex > 0) {
				String attribute = line.substring(fromIndex, endIndex);
				if (!attribute.contains(" ")) {
					attribute = removeQuotationsIfNecessary(attribute);
				}
				attributes.add(attribute);
				fromIndex = endIndex + 1;
			}
		}
		return attributes;
	}
	
	/**
	 * Remove quotations in oldLine if there are white chars inside them.
	 * @param oldLine. <code>String</code>
	 * @return new line
	 */
	public String removeQuotationsIfNecessary(String oldLine) {
		// Go over the line and gathers information about quotations and whites
		boolean quotationPresent = false;
		boolean mustKeepQuotation = false;
		int lengthString = oldLine.length();
		int firstQuotation = -1, lastQuotation = -1;
		for (int i = 0; i < lengthString; i++) {
			char c = oldLine.charAt(i);
			if (c == QUOTATION_CHAR) {
				if (!quotationPresent) {
					quotationPresent = true;
					firstQuotation = i;
				} else {
					lastQuotation = i;
				}
			} else if (c == WHITE_CHAR && quotationPresent) {
				mustKeepQuotation = true;
			}
		}
		
		String newLine = null;
		if (mustKeepQuotation) {
			if (lastQuotation != -1) {
				newLine = oldLine.substring(firstQuotation, lastQuotation);
			} else {
				newLine = oldLine;
			}
		} else {
			if (lastQuotation != -1) {
				newLine = oldLine.substring(firstQuotation+1, lastQuotation-1);
			} else {
				newLine = oldLine;
			}
		}
	 	
		return newLine;
	}
	
}
